import logging
import time
import uuid
import pymysql
import redis
from pymysql import cursors
import os

# 1、使用Redis管理登录令牌
# 功能要求：
# 用户登录-用户输入用户名密码登录-登录成功生成唯一token-token存入Redis，设置1小时过期
# 令牌验证-检查token是否存在-验证token是否过期-支持token自动续期（剩余时间<30分钟时）
# 基本管理-用户登出（删除token）-强制用户下线-查询用户登录状态
# 简单安全-记录登录IP-限制同一用户最多3个设备同时登录

# 配置日志
logging.basicConfig(level=logging.INFO, format="%(message)s")

# 常量定义
MINUTES_THRESHOLD = 30
SECONDS_PER_MINUTE = 60
DEFAULT_EXPIRE_TIME = 1800
DEFAULT_TTL_TIME = 3600
TEST_TTL_TIME = 10
TAKEN_ERROR = -808

# 当前用户标识符
USER_INFO = ""

# 从环境变量读取敏感信息
MYSQL_HOST = os.getenv('MYSQL_HOST', 'localhost')
MYSQL_PORT = int(os.getenv('MYSQL_PORT', 3306))
MYSQL_USER = os.getenv('MYSQL_USER', 'root')
MYSQL_PASSWORD = os.getenv('MYSQL_PASSWORD', '1234')
MYSQL_DATABASE = os.getenv('MYSQL_DATABASE', 'b_bighomework')

REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
REDIS_DB = int(os.getenv('REDIS_DB', 8))

# 建立MySQL数据库连接
mysql_connection = pymysql.connect(
    host=MYSQL_HOST,
    port=MYSQL_PORT,
    user=MYSQL_USER,
    password=MYSQL_PASSWORD,
    database=MYSQL_DATABASE,
    charset='utf8mb4',
    autocommit=False,
    cursorclass=cursors.DictCursor
)

# 建立Redis数据库连接
pool = redis.ConnectionPool(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB, decode_responses=True)
redis_client = redis.Redis(connection_pool=pool)

OPERATOR_DICT = {
    '1': "令牌验证",
    '2': "用户登出",
    '3': "强制用户下线",
    '4': "查询用户登录状态",
    '5': "查询用户登录状态"
}

def wait_and_log(message=""):
    """统一等待和日志输出"""
    if message:
        logging.info(message)
    time.sleep(2)


def login():
    """简单模拟登录场景"""
    logging.info("访客你好，请登录")
    wait_and_log()

    global USER_INFO

    while True:
        cursor = mysql_connection.cursor()
        sql = "SELECT * FROM redis_demo1_loginstimulate WHERE username = %s AND password = %s"

        str1 = input("请输入用户名：")
        str2 = input("请输入密码：")

        try:
            cursor.execute(sql, (str1, str2))
            result = cursor.fetchone()
        except pymysql.MySQLError as e:
            logging.error(f"MySQL查询失败: {e}")
            cursor.close()
            continue
        finally:
            cursor.close()

        if result:
            USER_INFO = result['username']

            # 生成会话唯一标识
            session_id = str(uuid.uuid4())
            user_session_key = f"username:{USER_INFO}:{session_id}"

            # 检查登录次数限制
            login_count_key = f"login_count:{USER_INFO}"
            try:
                current_count = redis_client.get(login_count_key)
                if current_count is not None and int(current_count) >= 3:
                    logging.warning(f"用户 {USER_INFO} 已达到最大登录设备数(3个)，无法继续登录")
                    wait_and_log()
                    continue
            except redis.RedisError:
                pass  # 如果检查失败，允许登录

            logging.info(f"你好{USER_INFO}，恭喜你登录成功！")

            new_uuid = uuid.uuid1()
            logging.info(f"当前生成了一个令牌 {new_uuid}")

            try:
                # 设置用户令牌（使用会话键）
                redis_client.setex(user_session_key, DEFAULT_TTL_TIME, str(new_uuid))
                # 增加登录次数计数
                redis_client.incr(login_count_key)
                # 设置计数器过期时间与令牌相同
                redis_client.expire(login_count_key, DEFAULT_TTL_TIME)
                # 记录会话信息，用于后续查询
                redis_client.sadd(f"user_sessions:{USER_INFO}", user_session_key)
                redis_client.expire(f"user_sessions:{USER_INFO}", DEFAULT_TTL_TIME)
            except redis.RedisError as e:
                logging.error(f"Redis设置失败: {e}")
                continue

            wait_and_log()
            data_info(USER_INFO, session_id)  # 传递会话ID

            return

        else:
            print("账户或密码错误，请重新输入")


def data_info(USER_INFO: str, session_id: str = None):
    """用户界面"""
    user_session_key = f"username:{USER_INFO}:{session_id}" if session_id else f"username:{USER_INFO}"

    while True:
        wait_and_log()
        print(f"欢迎你--{USER_INFO},你要处理什么操作")
        print("1.令牌验证")
        print("2.用户登出")
        print("3.强制用户下线")
        print("4.查询用户登录状态")

        while True:
            choice = input("请选择：").strip()
            if choice.isdigit() and int(choice) in range(1, 5):
                choice = int(choice)
                break
            else:
                print("输入无效，请输入数字选项。")

        logging.info(f"你选择了--{OPERATOR_DICT[str(choice)]},正在努力处理当前需求...")
        wait_and_log()

        actions = {
            1: token_check,
            2: logout,
            3: compel_logout,
            4: status_check
        }

        # 其他操作正常处理
        action = actions.get(choice)
        if action:
            # 统一通过返回值判断是否需要重新登录
            try:
                # 根据操作类型决定是否传递 session_id
                if choice in [1, 2, 4]:  # 需要 session_id 的操作
                    result = action(USER_INFO, session_id)
                else:  # 不需要 session_id 的操作
                    result = action(USER_INFO)
            except Exception as e:
                logging.error(f"执行操作时发生异常: {e}")
                result = True

            # 如果子操作返回 False，表示检测到未登录，需跳转到登录界面
            if result is False:
                logging.info("用户未登录，跳转到登录界面")
                wait_and_log()
                login()
                return

        logging.info("业务办理成功！")
        wait_and_log()

        while True:
            choice = input("是否继续办理其他业务？(Y/N): ").strip().upper()
            if choice == 'Y':
                break
            elif choice == 'N':
                logging.info("欢迎下次光临")
                wait_and_log()
                return
            else:
                logging.warning("输入无效，请重新输入")


def token_check(USER_INFO, session_id):
    """令牌验证板块"""
    user_session_key = f"username:{USER_INFO}:{session_id}"
    wait_and_log("*" * 40)

    def get_ttl(USER_INFO):
        try:
            ttl = redis_client.ttl(user_session_key)
            if ttl < 0:
                logging.warning(f"当前token状态异常，TTL值为: {ttl}")
                return TAKEN_ERROR
            rest_time = ttl / SECONDS_PER_MINUTE
            logging.info(f"当前token剩余时间为：{int(rest_time)} 分钟")
            return rest_time
        except redis.RedisError as e:
            logging.error(f"获取 TTL 失败: {e}")
            return TAKEN_ERROR

    def get_user_choice():
        while True:
            choice = input("Y/N: ").strip().upper()
            if choice in ('Y', 'N'):
                return choice
            logging.warning("输入无效，请输入 Y 或 N")

    try:
        if redis_client.exists(user_session_key):
            logging.info("键存在,当前用户已登录")
            wait_and_log()
            rest_time = get_ttl(USER_INFO)
            wait_and_log()

            if rest_time != TAKEN_ERROR and rest_time < MINUTES_THRESHOLD:
                logging.info("当前token剩余时间不足30分钟，是否续期（续期默认30分钟）")
                wait_and_log()
                sel = get_user_choice()
                if sel == 'Y':
                    redis_client.expire(user_session_key, DEFAULT_EXPIRE_TIME)
                    wait_and_log()
                    logging.info("续期成功！")
                    get_ttl(USER_INFO)
                    wait_and_log()
                else:
                    logging.info("已取消续期操作")
            wait_and_log("*" * 40)
            return True
        else:
            logging.info("键不存在，用户未登录")
            # 返回 False，通知上层跳转到登录界面
            return False
    except redis.RedisError as e:
        logging.error(f"Redis 操作失败: {e}")
        # 出错时不强制跳转登录，由上层决定
        return True


def logout(USER_INFO, session_id=None):
    """用户登出板块"""
    user_session_key = f"username:{USER_INFO}:{session_id}" if session_id else f"username:{USER_INFO}"
    wait_and_log("*" * 40)

    try:
        if redis_client.exists(user_session_key):
            # 删除用户令牌
            redis_client.delete(user_session_key)
            # 从会话集合中移除
            redis_client.srem(f"user_sessions:{USER_INFO}", user_session_key)
            # 减少登录次数计数
            login_count_key = f"login_count:{USER_INFO}"
            current_count = redis_client.get(login_count_key)
            if current_count is not None and int(current_count) > 0:
                redis_client.decr(login_count_key)
            wait_and_log()
            logging.info(f"用户 {USER_INFO} 已成功登出")
        else:
            logging.warning(f"用户 {USER_INFO} 未登录或已过期")
        USER_INFO = ""
    except redis.RedisError as e:
        logging.error(f"登出操作失败: {e}")

    wait_and_log("*" * 40)
    # 登出后通知上层需要重新登录
    return False


def compel_logout(USER_INFO):
    """强制用户下线，仅限root用户操作"""
    if USER_INFO != "root":
        wait_and_log("仅限root用户操作")
        return

    try:
        # 获取所有用户会话信息
        user_session_keys = redis_client.keys('username:*:*')  # 获取带会话ID的键

        if not user_session_keys:
            logging.info("当前没有用户在线")
            wait_and_log()
            return

        # 提取唯一用户名（不包括当前用户）
        unique_users = set()
        for key in user_session_keys:
            parts = key.split(':')
            if len(parts) >= 3:  # username:用户名:会话ID
                username = parts[1]
                if username != USER_INFO:  # 排除当前用户
                    unique_users.add(username)

        if not unique_users:
            logging.info("当前没有其他用户在线")
            wait_and_log()
            return

        logging.info("当前在线用户列表（本人除外）：")
        wait_and_log()

        # 显示用户列表并编号
        user_mapping = {}
        for idx, username in enumerate(sorted(unique_users), 1):
            # 获取该用户的登录次数
            login_count_key = f"login_count:{username}"
            login_count = redis_client.get(login_count_key) or "0"

            # 获取该用户的一个会话来显示剩余时间（任选一个会话）
            user_sessions = [key for key in user_session_keys if key.startswith(f"username:{username}:")]
            if user_sessions:
                sample_session = user_sessions[0]
                ttl = redis_client.ttl(sample_session)
                if ttl >= 0:
                    rest_time = ttl / SECONDS_PER_MINUTE
                    logging.info(f"  {idx}. {username} - 登录设备数:{login_count} - 剩余有效时间: {int(rest_time)} 分钟")
                    wait_and_log()
                else:
                    logging.info(f"  {idx}. {username} - 登录设备数:{login_count} - 状态异常")
                    wait_and_log()
            else:
                logging.info(f"  {idx}. {username} - 登录设备数:{login_count} - 无有效会话")
                wait_and_log()

            user_mapping[str(idx)] = username

        # 用户选择要强制下线的账户
        while True:
            choice = input("\n请选择要强制下线的用户编号（输入0取消）: ").strip()
            if choice == '0':
                logging.info("取消强制下线操作")
                wait_and_log()
                return
            elif choice in user_mapping:
                target_username = user_mapping[choice]
                try:
                    # 删除该用户的所有会话
                    user_sessions = [key for key in user_session_keys if key.startswith(f"username:{target_username}:")]
                    if user_sessions:
                        redis_client.delete(*user_sessions)
                        wait_and_log()
                        # 从会话集合中移除
                        redis_client.delete(f"user_sessions:{target_username}")
                        wait_and_log()
                        # 重置登录计数
                        login_count_key = f"login_count:{target_username}"
                        redis_client.delete(login_count_key)
                        wait_and_log()
                        logging.info(f"用户 {target_username} 已被强制下线（所有设备）")
                        wait_and_log()
                    else:
                        logging.info(f"用户 {target_username} 没有活动会话")
                        wait_and_log()
                    wait_and_log()
                except redis.RedisError as e:
                    logging.error(f"强制下线用户 {target_username} 失败: {e}")
                    wait_and_log()
                return
            else:
                logging.warning("输入无效，请重新输入有效的编号")
                wait_and_log()

    except redis.RedisError as e:
        logging.error(f"查询用户列表失败: {e}")
        return


def status_check(USER_INFO, session_id=None):
    """查询用户登录状态"""
    wait_and_log("*" * 40)

    try:
        # 获取用户所有会话
        user_sessions = redis_client.smembers(f"user_sessions:{USER_INFO}")

        if not user_sessions:
            logging.info("当前没有用户在线")
        else:
            logging.info("当前在线用户列表：")
            for session_key in user_sessions:
                # 提取用户名
                username = session_key.split(':')[1]
                # 检查是否为当前用户
                tag = " (本人)" if username == USER_INFO else ""
                ttl = redis_client.ttl(session_key)

                if ttl >= 0:
                    rest_time = ttl / SECONDS_PER_MINUTE
                    logging.info(f"  {username}{tag} - 剩余有效时间: {int(rest_time)} 分钟")
                else:
                    logging.info(f"  {username}{tag} - 状态异常")

        # 检查当前用户状态
        current_session_key = f"username:{USER_INFO}:{session_id}" if session_id else f"username:{USER_INFO}"
        if redis_client.exists(current_session_key):
            ttl = redis_client.ttl(current_session_key)
            if ttl >= 0:
                rest_time = ttl / SECONDS_PER_MINUTE
                logging.info(f"\n用户 {USER_INFO} 当前在线，剩余有效时间: {int(rest_time)} 分钟")
            else:
                logging.info(f"\n用户 {USER_INFO} 的登录状态异常")
                wait_and_log()
                return False
        else:
            logging.info(f"\n用户 {USER_INFO} 当前似乎已掉线")
            return False
    except redis.RedisError as e:
        logging.error(f"查询登录状态失败: {e}")

    wait_and_log("*" * 40)
    return True


if __name__ == '__main__':
    try:
        login()
    finally:
        redis_client.close()
        mysql_connection.close()
